package retry;

import cn.hutool.core.util.ObjectUtil;
import com.alibaba.fastjson.JSONObject;
import com.zwl.dropshipping.core.conf.properties.RetryableConf;
import com.zwl.dropshipping.core.conf.properties.RetryableProperties;
import com.zwl.dropshipping.core.enums.MqyyyyProductStatusEnum;
import com.zwl.dropshipping.core.service.EventRetryLogService;
import com.zwl.dropshipping.jd.enums.JdApiEnum;
import com.zwl.yyyy.common.core.retry.annotation.Retryable;
import com.zwl.yyyy.common.core.spel.OperationExpressionEvaluator;
import com.zwl.yyyy.common.kit.utils.StringUtils;
import com.zwl.yyyy.common.kit.utils.busi.LogUtil;
import com.zwl.yyyy.common.util.CheckUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.Map;

/**
* @author zhangweilin
* @date 2023/10/10 15:26
* @Description:
*/

@Slf4j
@RequiredArgsConstructor
@Aspect
@Component
public class RetryableCompensationAspect {

    @Pointcut("@annotation(com.zwl.zwl.common.core.retry.annotation.Retryable)")
    public void compensationPointcut() {
    }

	@Autowired
	private EventRetryLogService eventRetryLogService;

	protected final OperationExpressionEvaluator evaluator = new OperationExpressionEvaluator();

	@Autowired
	private RetryableConf retryableConf;

	private static final Integer mysqlTextMaxLen = 65535;
	private static final Integer mysqlMediumTextMaxLen = 16777215;

	/**
	 * 多参数分隔符
	 */
	public static final String paramSplitCharacter  = "@@@###@@@";

	public static final ThreadLocal<Boolean> retryCallThreadLocal = new ThreadLocal<>();

    @Around("compensationPointcut()")
    public Object compensate(ProceedingJoinPoint joinPoint) throws Throwable {
        Object result = null;
		Object[] args = joinPoint.getArgs();
		try {
			if (CheckUtil.isNotEmpty(args)) {
				result = joinPoint.proceed(args);
			} else {
				result = joinPoint.proceed();
			}
		} catch (Throwable e) {
			Boolean isRetry= retryCallThreadLocal.get();
			if (null != isRetry && isRetry) {
				retryCallThreadLocal.remove();
				throw e;
			}
			LogUtil.error(log,e,"出现异常");
            MethodSignature signature = (MethodSignature) joinPoint.getSignature();
            Method method = signature.getMethod();
			Retryable retryable = AnnotationUtils.getAnnotation(method, Retryable.class);
			if (null == retryable) {
				return result;
			}
			//有参
			StringBuilder paramClassNameSb = new StringBuilder();
			StringBuilder paramValueSb = new StringBuilder();
			if (CheckUtil.isNotEmpty(args)) {
				for (Object arg : args) {
					String paramClassName = arg.getClass().getName();
					if(paramClassNameSb.length() > 0){
						paramClassNameSb.append(paramSplitCharacter);
					}
					paramClassNameSb.append(paramClassName);

					if (paramValueSb.length() > 0) {
						paramValueSb.append(paramSplitCharacter);
					}
					paramValueSb.append(JSONObject.toJSONString(arg));
				}
			}

			String bizType = retryable.bizType();
			EventRetryLogContentEntity eventRetryLogContentEntity = new EventRetryLogContentEntity();
			String content = paramValueSb.toString();
			eventRetryLogContentEntity.setContent(content);
			StackTraceElement[] stackTraceElements = e.getStackTrace();
			StringBuilder stackTraceSb = new StringBuilder();
			for (StackTraceElement element : stackTraceElements) {
				stackTraceSb.append(element.toString());
				stackTraceSb.append("\n");
			}
			String stackTraceString = stackTraceSb.toString();
			stackTraceString = StringUtils.substring(stackTraceString,0, mysqlTextMaxLen);
			eventRetryLogContentEntity.setExceptionStackInfo(stackTraceString);

			//没配置此type，表示不打算走apollo配置，如果不走apollo配置，那么直接落库注解上的默认值
			EventRetryLogEntity eventRetryLogEntity = initEventRetryLogEntity(joinPoint, e, retryable, paramClassNameSb);
			if (CheckUtil.isEmpty(bizType)) {
				fillDefaultValue(retryable, eventRetryLogEntity);
			} else {
				//从apollo取
				Map<String, RetryableProperties> confMap = retryableConf.getMap();
				if (CheckUtil.isEmpty(confMap)) {
					fillDefaultValue(retryable, eventRetryLogEntity);
				} else {
					bizType = JdApiEnum.getValue(bizType);
					RetryableProperties retryableProperties = confMap.get(bizType);
					if (retryableProperties == null) {
						fillDefaultValue(retryable, eventRetryLogEntity);
					} else {
						fillConfigValue(retryable, eventRetryLogEntity, retryableProperties);
					}
				}
			}

			if (content.length() > mysqlMediumTextMaxLen) {
				LogUtil.error(log,"重试逻辑中，入参长度大于mysql Medium Text最大长度【{}】的限制,eventRetryLogEntity:{},eventRetryLogContentEntity:{}",mysqlMediumTextMaxLen,eventRetryLogEntity,eventRetryLogContentEntity);
				return result;
			}
			String key = retryable.key();
			Object keyObj = evaluator.getKeyValue(key, method, args, joinPoint.getTarget());
			if (null!=keyObj) {
				eventRetryLogEntity.setBizCode(keyObj.toString());
			}
			eventRetryLogService.save(eventRetryLogEntity,eventRetryLogContentEntity);
			if (retryable.isExpContinueThrow()) {
				throw e;
			}
        }
        return result;
    }

	private EventRetryLogEntity initEventRetryLogEntity(ProceedingJoinPoint joinPoint, Throwable e, Retryable retryable,
			StringBuilder paramClassNameSb) {
		EventRetryLogEntity eventRetryLogEntity = new EventRetryLogEntity();
		eventRetryLogEntity.setBeanClassName(joinPoint.getTarget().getClass().getName());
		eventRetryLogEntity.setMethodName(joinPoint.getSignature().getName());
		eventRetryLogEntity.setParamClassName(paramClassNameSb.toString());
		eventRetryLogEntity.setExceptionMessage(e.getMessage());
		eventRetryLogEntity.setStatus(MqyyyyProductStatusEnum.INIT.getValue());
		eventRetryLogEntity.setRetryTimes(0);
		eventRetryLogEntity.setBizType(retryable.bizType());
		return eventRetryLogEntity;
	}

	private void fillConfigValue(Retryable retryable, EventRetryLogEntity eventRetryLogEntity, RetryableProperties retryableProperties) {
		eventRetryLogEntity.setUseSelfBeanFlag(ObjectUtil.defaultIfNull(retryableProperties.getUseSelfBeanFlag(), retryable.useSelfBeanFlag()));
		eventRetryLogEntity.setMaxRetryTimes(ObjectUtil.defaultIfNull(retryableProperties.getMaxRetryTimes(), retryable.maxRetryTimes()));
		eventRetryLogEntity.setRetryTimesWarnThreshold(ObjectUtil.defaultIfNull(retryableProperties.getRetryTimesWarnThreshold(), retryable.retryTimesWarnThreshold()));
		eventRetryLogEntity.setContinuousWarnFlag(ObjectUtil.defaultIfNull(retryableProperties.getContinuousWarnFlag(), retryable.continuousWarnFlag()));
		eventRetryLogEntity.setContinuousWarnInterval(ObjectUtil.defaultIfNull(retryableProperties.getContinuousWarnInterval(), retryable.continuousWarnInterval()));
		eventRetryLogEntity.setContinuousWarnMaxCount(ObjectUtil.defaultIfNull(retryableProperties.getContinuousWarnMaxCount(), retryable.continuousWarnMaxCount()));
	}

	private void fillDefaultValue(Retryable retryable, EventRetryLogEntity eventRetryLogEntity) {
		eventRetryLogEntity.setUseSelfBeanFlag(retryable.useSelfBeanFlag());
		eventRetryLogEntity.setMaxRetryTimes(retryable.maxRetryTimes());
		eventRetryLogEntity.setRetryTimesWarnThreshold(retryable.retryTimesWarnThreshold());
		eventRetryLogEntity.setContinuousWarnFlag(retryable.continuousWarnFlag());
		eventRetryLogEntity.setContinuousWarnInterval(retryable.continuousWarnInterval());
		eventRetryLogEntity.setContinuousWarnMaxCount(retryable.continuousWarnMaxCount());
	}

}
